import { DevConfig, WebBundle, BuildOptions } from "./types";
import { extname, dirname } from "path";
import { now, printLog, asNames, CallReducer } from "./utils";
import { async as glob } from 'fast-glob';
import { rollupFiles, pcssFiles, tmplFiles } from "./processors";
import { ensureDir } from "fs-extra-plus";
import * as chokidar from 'chokidar';


const rollupOptionsDefaults = {
	ts: {
		watch: false,
		ts: true,
		tsconfig: './tsconfig.json'
	},
	js: {
		watch: false,
		ts: false
	}
}

const processorsByType: { [name: string]: Function } = {
	js: buildJsBundler,
	ts: buildTsBundler,
	tmpl: buildTmplBundler,
	pcss: buildPcssBundler
}

/**
 * Initialize all of the bundle properties accordingly. 
 * This allow the bundlers and other logic to not have to worry about default values and path resolution.
 */
export async function initWebBundle(configs: DevConfig): Promise<DevConfig> {
	for (const bundle of configs.bundles) {
		bundle.type = extname(asNames(bundle.entries)[0]).substring(1);
		// --------- rollupOptions initialization --------- //
		if (bundle.type === 'ts' || bundle.type === 'js') {
			// get the base default options for this type
			// override default it if bundle has one
			bundle.options = { ...rollupOptionsDefaults[bundle.type], ...bundle.options };
		}
		// --------- /rollupOptions initialization --------- //
	}

	return configs;
}
// --------- /Private WebBundle Utils --------- //

// --------- Private --------- //
export async function buildWebBundle(configs: DevConfig, opts?: BuildOptions) {
	// Note: if we have a bundleName, then, just the bundle log will be enough.
	let watch = false;
	if (opts && opts.watch) {
		watch = true;
	}
	if (!watch) {
		console.log(`------ Building Web Bundle ------`);
	} else {
		console.log(`------ Watching Web Bundle ------`);
	}

	for (const bundle of configs.bundles) {
		const start = now();
		const type = bundle.type!;
		const processor = processorsByType[type];
		await ensureDir(dirname(bundle.dist));
		await processor(bundle, opts);
		await printLog(`Building bundle ${bundle.name}`, bundle.dist, start);
	}

	if (!watch) {
		console.log(`------ Building Web Bundle DONE ------\n`);
	}
}
// --------- /Private --------- //


// async function runTsc(path: string) {
// 	await spawn('tsc', [], { cwd: block.dir });
// }

// --------- Private Bundlers --------- //
async function buildTsBundler(bundle: WebBundle, opts?: BuildOptions) {
	// TODO: need to re-enable watch
	try {
		if (opts && opts.watch) {
			bundle.options.watch = true;
		}
		// resolve all of the entries (with glob)
		const allEntries = await resolveGlobs(bundle.entries);
		await rollupFiles(allEntries, bundle.dist, bundle.options);
	} catch (ex) {
		// TODO: need to move exception ahndle to the caller
		console.log("BUILD ERROR - something when wrong on rollup\n\t", ex);
		console.log("Empty string was save to the app bundle");
		console.log("Trying saving again...");
		return;
	}
}

async function buildPcssBundler(bundle: WebBundle, opts?: BuildOptions) {
	let allEntries = await resolveGlobs(bundle.entries);

	if (opts && opts.watch) {
		const cr = new CallReducer(async () => {
			allEntries = await resolveGlobs(bundle.entries);
			await pcssFiles(allEntries, bundle.dist);
		}, 1000);

		const watcher = chokidar.watch(bundle.entries, { persistent: true });
		watcher.on('change', async function (filePath: string, stats) {
			if (filePath.endsWith(`.pcss`)) {
				console.log(`change ${filePath}`);
				cr.map(filePath);
			}
		});

		watcher.on('add', async function (filePath: string) {
			if (filePath.endsWith(`.pcss`)) {
				console.log(`add ${filePath}`);
				cr.map(filePath);
			}
		});
	} else {
		await pcssFiles(allEntries, bundle.dist);
	}
}

async function buildTmplBundler(bundle: WebBundle, opts?: BuildOptions) {
	let allEntries = await resolveGlobs(bundle.entries);
	if (opts && opts.watch) {
		const cr = new CallReducer(async () => {
			allEntries = await resolveGlobs(bundle.entries);
			await tmplFiles(allEntries, bundle.dist);
		}, 1000);
		const watcher = chokidar.watch(bundle.entries, { persistent: true });
		watcher.on('change', async function (filePath: string, stats) {
			if (filePath.endsWith(`.tmpl`)) {
				console.log(`change ${filePath}`);
				cr.map(filePath);
			}
		});

		watcher.on('add', async function (filePath: string) {
			if (filePath.endsWith(`.tmpl`)) {
				console.log(`add ${filePath}`);
				cr.map(filePath);
			}
		});
	} else {
		await tmplFiles(allEntries, bundle.dist);
	}
}

async function buildJsBundler(bundle: WebBundle, opts?: BuildOptions) {
	if (opts && opts.watch) {
		bundle.options.watch = true;
	}
	const allEntries = await resolveGlobs(bundle.entries);
	await rollupFiles(allEntries, bundle.dist, bundle.options);
}
// --------- /Private Bundlers --------- //

async function resolveGlobs(globs: string | string[]) {
	// resolve all of the entries (with glob)
	return (await glob(globs)).map(entryItem => (typeof entryItem === 'string') ? entryItem : entryItem.path);
}